package storagex

import (
	"bytes"
	"context"
	"fmt"
	"strings"

	"github.com/qiniu/go-sdk/v7/auth"
	"github.com/qiniu/go-sdk/v7/auth/qbox"
	"github.com/qiniu/go-sdk/v7/storage"
)

// 七牛云存储
type qiniuStorage struct {
	bucket    string
	accessKey string
	secretKey string
	zone      *storage.Region
	domain    string
}

func NewKodoStorage(cfg Config) *qiniuStorage {
	zone, ok := storage.GetRegionByID(storage.RegionID(cfg.EndPoint))
	if !ok {
		return &qiniuStorage{}
	}
	return &qiniuStorage{
		bucket:    cfg.Bucket,
		accessKey: cfg.AccessKey,
		secretKey: cfg.SecretKey,
		zone:      &zone,
		domain:    cfg.Domain,
	}
}

func (s *qiniuStorage) mac() *auth.Credentials {
	return qbox.NewMac(s.accessKey, s.secretKey)
}

func (s *qiniuStorage) config() *storage.Config {
	return &storage.Config{
		Region:   s.zone,
		UseHTTPS: true,
	}
}

func (s *qiniuStorage) bucketManager() *storage.BucketManager {
	return storage.NewBucketManager(s.mac(), s.config())
}

func (s *qiniuStorage) context() context.Context {
	return context.Background()
}

func (s *qiniuStorage) Ticket() string {
	putPolicy := storage.PutPolicy{Scope: s.bucket}
	return putPolicy.UploadToken(s.mac())
}

func (s *qiniuStorage) TicketWithOverride(remoteKey string) string {
	putPolicy := storage.PutPolicy{Scope: fmt.Sprintf("%s:%s", s.bucket, remoteKey)}
	return putPolicy.UploadToken(s.mac())
}

func (s *qiniuStorage) UploadFile(remoteKey, localFile string, override bool) (string, error) {
	ticket := ""
	if override {
		ticket = s.TicketWithOverride(remoteKey)
	} else {
		ticket = s.Ticket()
	}
	var ret storage.PutRet
	err := storage.NewFormUploader(s.config()).PutFile(s.context(), &ret, ticket, remoteKey, localFile, &storage.PutExtra{})
	return ret.Hash, err
}

func (s *qiniuStorage) UploadBytes(remoteKey string, fBytes []byte, override bool) (string, error) {
	ticket := ""
	if override {
		ticket = s.TicketWithOverride(remoteKey)
	} else {
		ticket = s.Ticket()
	}
	var ret storage.PutRet
	err := storage.NewFormUploader(s.config()).Put(s.context(), &ret, ticket, remoteKey, bytes.NewReader(fBytes), int64(len(fBytes)), &storage.PutExtra{})
	return ret.Hash, err
}

func (s *qiniuStorage) UploadBigFile(remoteKey, localFile string, override bool) (string, error) {
	ticket := ""
	if override {
		ticket = s.TicketWithOverride(remoteKey)
	} else {
		ticket = s.Ticket()
	}
	var ret storage.PutRet
	err := storage.NewResumeUploaderV2(s.config()).PutFile(s.context(), &ret, ticket, remoteKey, localFile, &storage.RputV2Extra{})
	return ret.Hash, err
}

func (s *qiniuStorage) Stat(remoteKey string) (*FileInfo, error) {
	info, err := s.bucketManager().Stat(s.bucket, remoteKey)
	if err != nil {
		return nil, err
	}
	return &FileInfo{
		Key:        remoteKey,
		Size:       info.Fsize,
		Hash:       info.Hash,
		Md5:        info.Md5,
		MimeType:   info.MimeType,
		CreateTime: info.PutTime,
	}, nil
}

func (s *qiniuStorage) Delete(remoteKey string) error {
	return s.bucketManager().Delete(s.bucket, remoteKey)
}

func (s *qiniuStorage) Move(srcKey, destKey string, force bool) error {
	return s.bucketManager().Move(s.bucket, srcKey, s.bucket, destKey, force)
}

func (s *qiniuStorage) Copy(srcKey, destKey string, force bool) error {
	return s.bucketManager().Copy(s.bucket, srcKey, s.bucket, destKey, force)
}

func (s *qiniuStorage) MakePublicKey(remoteKey string) string {
	return storage.MakePublicURLv2(s.domain, remoteKey)
}

func (s *qiniuStorage) MakePrivateKey(remoteKey string, expires int64) string {
	return storage.MakePrivateURLv2(s.mac(), s.domain, remoteKey, expires)
}

func (s *qiniuStorage) List(dir, marker string, limit int) ([]FileInfo, string, error) {
	entries, _, nextMarker, hasNext, err := s.bucketManager().ListFiles(s.bucket, dir, "", marker, limit)
	if err != nil {
		return nil, "", err
	}

	files := make([]FileInfo, 0)
	for _, entry := range entries {
		mimeType := entry.MimeType
		isDir := false
		if strings.HasSuffix(entry.Key, "/") || mimeType == "application/qiniu-object-manager" {
			isDir = true
		}
		if mimeType == "application/qiniu-object-manager" {
			mimeType = ""
		}
		files = append(files, FileInfo{
			Key:        entry.Key,
			Size:       entry.Fsize,
			Hash:       entry.Hash,
			Md5:        entry.Md5,
			MimeType:   mimeType,
			CreateTime: entry.PutTime,
			IsDir:      isDir,
		})
	}

	if !hasNext {
		nextMarker = ""
	}
	return files, nextMarker, nil
}
